Mind Circle
https://gyazo.com/b7584c0b06014c2950efb23f50209922
中心の円に(メイン)テーマを置き、その周辺にサブテーマを置く。
code:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Mind Circle - 思考サポートツール</title>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Hiragino Kaku Gothic ProN', 'Meiryo', sans-serif;
}
width: 100vw;
height: 100vh;
display: block;
}
/* 円のスタイル */
.theme-circle {
stroke-width: 4;
}
.sub-circle {
stroke-width: 3;
}
/* テキスト入力エリア */
.label-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
text-align: center;
cursor: pointer;
}
.label-text {
font-size: 20px;
font-weight: bold;
word-break: break-all;
padding: 10px;
outline: none;
line-height: 1.2;
width: 100%;
display: inline-block;
}
.sub-text {
font-size: 16px;
font-weight: normal;
}
/* プラスボタン */
position: fixed;
bottom: 30px;
right: 30px;
width: 65px;
height: 65px;
border-radius: 50%;
color: white;
border: none;
font-size: 35px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
transition: transform 0.1s;
}
transform: scale(0.9);
}
</style>
</head>
<body>
<button id="add-btn">+</button>
<svg id="canvas"></svg>
<script>
const canvas = document.getElementById('canvas');
let subThemes = [];
let mainNode = null;
const mainRadius = 60 * 1.7;
const subRadius = 40 * 1.7;
const distance = 240;
function init() {
mainNode = createNode("メインテーマ", true);
updatePositions();
window.addEventListener('resize', updatePositions);
document.getElementById('add-btn').onclick = () => {
const newNode = createNode("サブテーマ", false);
subThemes.push(newNode);
updatePositions();
// 生成後、少し待ってから(描画確定後)フォーカスを当てる
setTimeout(() => {
enterEditMode(newNode.span);
}, 50);
};
}
// 編集モードに入る共通処理
function enterEditMode(element) {
element.contentEditable = true;
element.focus();
// テキストを全選択する
const range = document.createRange();
range.selectNodeContents(element);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function createNode(initialText, isMain) {
const r = isMain ? mainRadius : subRadius;
circle.setAttribute("r", r);
circle.setAttribute("class", isMain ? "theme-circle" : "sub-circle");
fo.setAttribute("width", r * 2);
fo.setAttribute("height", r * 2);
const div = document.createElement("div");
div.className = "label-container";
const span = document.createElement("span");
span.className = isMain ? "label-text" : "label-text sub-text";
span.innerText = initialText;
span.contentEditable = false;
// ダブルクリックイベント
div.ondblclick = (e) => {
e.stopPropagation();
enterEditMode(span);
};
// Enterキーで編集を確定(改行させない)
span.onkeydown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
span.blur();
}
};
span.onblur = () => {
span.contentEditable = false;
};
div.appendChild(span);
fo.appendChild(div);
g.appendChild(circle);
g.appendChild(fo);
canvas.appendChild(g);
return { group: g, circle: circle, fo: fo, radius: r, span: span };
}
function updatePositions() {
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
setElementPos(mainNode, centerX, centerY);
const count = subThemes.length;
subThemes.forEach((node, i) => {
const angle = (2 * Math.PI * i / count) - (Math.PI / 2);
const x = centerX + distance * Math.cos(angle);
const y = centerY + distance * Math.sin(angle);
setElementPos(node, x, y);
});
}
function setElementPos(node, x, y) {
node.circle.setAttribute("cx", x);
node.circle.setAttribute("cy", y);
node.fo.setAttribute("x", x - node.radius);
node.fo.setAttribute("y", y - node.radius);
}
init();
</script>
</body>
</html>